Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Resolve question finder default context race conditions #1131

Open
wants to merge 11 commits into
base: master
Choose a base branch
from

Conversation

sjd210
Copy link
Contributor

@sjd210 sjd210 commented Sep 17, 2024

Fixes some issues with users sometimes being fully- or partially-locked into default user context rather than their own preferences when loading the Question Finder (hence showing either all or no questions, rather than their custom filters).

Also changes the default text for if no filters have been applied on first loading the page.
No results match your criteria -> Please select and apply filters


To recreate the issue, load https://adacomputerscience.org/questions with no URL parameters and My Account > Customize > Show me content for... [GCSE] [AQA] (or some other non-default combination).

On my end, I was getting ALL questions on first page load, then NO questions upon refreshing that page, followed by the correct filters applying every subsequent refresh (although I wouldn't be surprised if this was device-specific).

To fix this, we check if the filters were previously based on the default context (userContextDefault), and reset them if we now have a non-default context.

Copy link

codecov bot commented Sep 17, 2024

Codecov Report

Attention: Patch coverage is 0% with 15 lines in your changes missing coverage. Please review.

Project coverage is 36.53%. Comparing base (95ada0b) to head (82be94c).
Report is 92 commits behind head on master.

Files with missing lines Patch % Lines
src/app/components/pages/QuestionFinder.tsx 0.00% 15 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##           master    #1131      +/-   ##
==========================================
+ Coverage   36.50%   36.53%   +0.02%     
==========================================
  Files         429      432       +3     
  Lines       19120    19222     +102     
  Branches     5653     5667      +14     
==========================================
+ Hits         6980     7023      +43     
- Misses      12102    12161      +59     
  Partials       38       38              

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

Copy link
Contributor

@mlt47 mlt47 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's good that you've found a solution.
What we are aiming for is to set the filter options whenever the user, or maybe its user context, changes. Rather than adding an extra useState to get around the problem, do we know why that intended behaviour is not being achieved by the populateFromUserContext useEffect? Happy to chat through this as it is quite a tricky bit of code!

src/app/components/pages/QuestionFinder.tsx Outdated Show resolved Hide resolved
src/app/components/pages/QuestionFinder.tsx Outdated Show resolved Hide resolved
@sjd210
Copy link
Contributor Author

sjd210 commented Sep 26, 2024

Rather than adding an extra useState to get around the problem, do we know why that intended behaviour is not being achieved by the populateFromUserContext useEffect? Happy to chat through this as it is quite a tricky bit of code!

@mlt47 Digging a little bit deeper, I've found that the crux of the issue seems to be setPopulatedUserContext updating at the wrong times. An empty array is truthy, so searchStages and searchExamBoards can update it to a populated user context state without being the correctly populated values (which is why the extra searchAndUpdateURL() I added was needed as a workaround in the previous solution above)
(I was looking at the wrong variables here, but the core idea still applies. We rely on populatedUserContext to tell us when loading has completed, but it finishes earlier than that before other set...s have applied).

It seems with some quick testing like it can be solved fairly simply by separating setPopulatedUserContext into its own useEffect with dependencies on the actual search terms or something similar (with no need for the extra useState or other work I've done here), but I'll have to experiment with how this works in different contexts to confirm this.

@sjd210
Copy link
Contributor Author

sjd210 commented Sep 27, 2024

This now boiled down to two main changes:
1 - Changing the check for an array of pre-existing filters (as obtained from the URL parameters) into a check for the existence of those URL parameters directly.

This is needed so an initial 'default' user state doesn't take precedence over the real user context, but URL parameters do in order to allow the page to be refreshed/copied/etc.

2 - Making the searchAndUpdateURL() useEffect have the search parameters as dependencies so it continues to be checked later during the loading phase, conditional on the search parameters matching those from user context.

This is needed so searchAndUpdateURL() is not called early due to userContext being loaded in without React finishing processing the setStages and setExamBoards functions. We know the set... functions have finished when the parameters successfully match.

@sjd210 sjd210 requested a review from mlt47 September 27, 2024 08:35
The previous code set the stage and exam board settings from the user's viewing context. Although the naming is a little complicated, the user's viewing context is not the same as the user's account level registered contexts. It is those account level registered contexts that we are interested in for pre-populating filters.
If the user comes to the question finder with query params specifying the stages and exambaords filter settings they take presedence over account settings.
@sjd210
Copy link
Contributor Author

sjd210 commented Oct 8, 2024

@mlt47 I think your change makes a lot of sense. Only once a user object is instantiated will they will be logged in, at which point the object is also instantiated enough to include a registeredContext. It works very simply and cleanly most of the time.

However, I think this solution fails to cover some of the edge cases:

  • Most notably, currently a logged out user on Ada will have default preferences applied on page-load, so make a search for all "Ada" Exam Board tagged items (which is all items). This disabled that page load search.
    Physics doesn't have any default logged-out search parameters, so it doesn't change anything there (unless we want to specifically add a set now).
    [James said that we should probably just have nothing for both page loads, in which case this is fine]

  • Also somewhat notably, if a user has URL parameters other than examBoards and stages set, their blank examBoards/stages filters will be overwritten by their preferred ones on reload.
    e.g. Difficulty: P1, ExamBoard: {}, Stage: {} -> Difficulty: P1, ExamBoard: AQA, Stage: GCSE
    We should probably make filtersHaveNotBeenSpecifiedByQueryParams check for the existence of any URL search parameters. My solution also had this same problem, but I've just noticed it here.

  • Not much of an issue but for the sake of keeping record, a user can have no registered preferred stage context on Physics if they keep ignoring the modal to add one - and therefore will have nothing show up on loading the page. I think this is probably correct unless we did decide to add default Physics search parameters, in which case they should probably also be applied in this case.

  • On a similar-ish note for a problem that's mostly pre-existing but is highlighted here, if a Physics user has "All stages" set as their preferred Stage, the meaningless parameter "all" will be automatically set. This makes little practical difference to the searching as no questions are tagged with "all", but it adds an invisible count to the Stage filter and appears in the URL.
    I feel that either we should set "all" as a filter that actually does search for all questions (similar to the "Ada" exam board on Ada) OR simply disallow it from the filters. (This can be its own card)

const [populatedFromAccountSettings, setPopulatedFromAccountSettings] = useState(false);
useEffect(function populateFiltersFromAccountSettings() {
if (isLoggedIn(user)) {
const filtersHaveNotBeenSpecifiedByQueryParams = !params.stages && !params.examBoards;
Copy link
Contributor Author

@sjd210 sjd210 Oct 9, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this is the only blocking change. Anything else like removing the meaningless "all" tag can be made into its own card, since I think we're deciding that having no filters applied when logged out is fine.

Suggested change
const filtersHaveNotBeenSpecifiedByQueryParams = !params.stages && !params.examBoards;
const filtersHaveNotBeenSpecifiedByQueryParams = Object.keys(params).every(param => ["stage","examBoard"].includes(param) || !params[param]);

There are probably other ways to handle this line, but this seems clean to me.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants